package com.github.server;

import android.util.ArrayMap;

import com.github.livebase.log.Logger;
import com.github.livebase.log.LoggerFactory;
import com.github.livebase.util.base.CollectionsUtil;
import com.github.server.core.AntPathMatcher;
import com.github.server.core.HttpRequest;
import com.github.server.core.HttpResponse;
import com.github.server.core.SimpleMapping;
import com.github.server.interfaces.HttpApplication;
import com.github.server.interfaces.HttpHandler;
import com.github.server.interfaces.ResourceMapping;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;

import fi.iki.elonen.NanoHTTPD;

/**
 * @ClassName Application
 * @description:
 * @author: biao532
 * @create: 2024-01-31 11:13
 * @Version 1.0
 **/

public class Application extends NanoHTTPD implements HttpApplication {
    private static Logger logger = LoggerFactory.getLogger(Application.class);
    private class HandlerDetail {
        long arrived;
        ClientHandler clientHandler;
        HandlerDetail(ClientHandler handler) {
            this(System.currentTimeMillis(), handler);
        }
        HandlerDetail(long arrived, ClientHandler handler) {
            this.arrived = arrived;
            this.clientHandler = handler;
        }
    }
    public class IAsyncRunner implements AsyncRunner, Runnable {
        private BlockingDeque<HandlerDetail> handlers;
        private boolean isRunning = true;
        public IAsyncRunner() {
            handlers = new LinkedBlockingDeque<>();
        }
        @Override
        public void closeAll() {
            if (isRunning) {
                isRunning = false;
            }
            List<HandlerDetail> h = new ArrayList<>(handlers);
            handlers.clear();
            isRunning = true;
            CollectionsUtil.foreach(h, (ha) -> {
                if (ha != null) {
                    ha.clientHandler.close();
                }
            });
        }
        @Override
        public void closed(ClientHandler clientHandler) {

        }

        @Override
        public void exec(ClientHandler code) {
            if (!isRunning) {
                code.close();
                return;
            }
            handlers.offer(new HandlerDetail(code));
        }

        @Override
        public void run() {
            while (!Thread.interrupted()) {
                try {
                    logger.trance("IIAsyncRunner", "waitfor toke size={}", handlers.size());
                    HandlerDetail handler = handlers.take();
                    logger.trance("IIAsyncRunner", "toke success t={}, h={}", handler.arrived, handler.clientHandler);
                    if (System.currentTimeMillis() - handler.arrived > 3000) {
                        logger.debug("IIAsyncRunner", "close clientHandler");
                        handler.clientHandler.close();
                        continue;
                    }
                    logger.debug("IIAsyncRunner", "clientHandler run start");
                    handler.clientHandler.run();
                    logger.debug("IIAsyncRunner", "clientHandler run start end");
                } catch (InterruptedException e) {
                    logger.error("IIAsyncRunner", e,"clientHandler run error");
                }
            }
        }
    }

    private AntPathMatcher matcher;
    private List<ResourceMapping> mappings;
    private Thread asyncRunner;
    public Application(int port) {
        super(port);
        mappings = new ArrayList<>();
        matcher = new AntPathMatcher();
    }

    public Application(String hostname, int port) {
        super(hostname, port);
    }
    public void registerHandler(String path, HttpHandler handler) {
        registerHandler(new SimpleMapping(path, handler));
    }

    public void registerHandler(ResourceMapping mapping, int index) {
        logger.debug("Application", "registerHandler[path={}, handler={}] index is {}", mapping.getPath(), mapping.getHandler(), index);
        if (index < 0 || index > mappings.size()) {
            mappings.add(mapping);
        }
        else {
            mappings.add(index, mapping);
        }
    }
    public void registerHandler(ResourceMapping mapping) {
        registerHandler(mapping, -1);
    }
    protected ResourceMapping match(String path) {
        return CollectionsUtil.first(mappings, (m) -> matcher.match(m.getPath(), path));
    }

    @Override
    public void start() throws IOException {
        logger.info("Application", "run start server");
        IAsyncRunner c = new IAsyncRunner();
        asyncRunner = new Thread(c);
        asyncRunner.start();
        setAsyncRunner(c);
        super.start();
        logger.info("Application", "server start success");
    }

    @Override
    public void stop() {
        super.stop();
        if (asyncRunner != null) {
            asyncRunner.interrupt();
        }
    }

    @Override
    public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms, Map<String, String> files) {
        Map<String, String> parameter = new ArrayMap<>();
        parameter.putAll(parms);
        parameter.putAll(files);
        ResourceMapping handler = match(uri);
        logger.debug("Application", "match {} for {} parameter is {}", handler, uri, parameter);
        if (handler == null) {
            return newFixedLengthResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, Response.Status.NOT_FOUND.getDescription());
        }
        HttpRequest request = new HttpRequest(uri, method.name(), parameter, headers);
        HttpResponse response = new HttpResponse();
        try {
            handler.getHandler().handler(request, response);
        }
        catch (RuntimeException | Error error) {
            logger.warn("Application", error,"在处理url[{}]时发生错误", uri);
            return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, Response.Status.INTERNAL_ERROR.getDescription());
        }
        return response;
    }
}
